<?php
/**
 * Klasa ułatwiająca obsługę importowania danych z plików XLS.
 * Programista musi dziedziczyć z tej klasy i przeciążyć metodę setu
 * w której powinien zdefiniować schemat pliku XLS w formacie nazwa_pola => validator.
 * Kolejność wprowadzania elementów do tablicy ma znaczenie - zostanie to zmapowane
 * wg kolejności na komórki.
 *
 * @author agrzelak
 *
 */
abstract class SRM_Data_Import_ExcelSchema
{
	/**
	 * Czy wykonywać metodę postProcessRow na każdym zwalidowanym wierszu wynikowym?
	 */
	protected $doPostprocessing = true;
	/**
	 * Czy każde pole wiersze potraktować htmlspecialchars ?
	 * @var boolean
	 */
	protected $doGlobalEscapeHTML = true;
	/**
	 * Czy wyrzucać wyjątek w przypadku, gdy wystąpił błąd podczas walidacji?
	 * @var boolean
	 */
	protected $raiseExceptionOnError = true;
	protected $labelSchema = array();

	/**
	 * Inicjalizacji parsera
	 * @return
	 */
	public function __construct($omitFirstRow = true)
	{
		$this->schema = new sfValidatorSchema();
		$this->errorRows = array();
		$this->indexMap = array();
		$this->omitFirstRow = $omitFirstRow;

		$this->setup();
	}

	/**
	 * Ustawia schemat pliku
	 * @param array $schema
	 * @return
	 */
	public function setSchema($schema) {
		$iter = 1;
		// korzystamy z własności php ze kolejność iterowania elementow słownika
		// jest taka sama jak przy wstawianiu elementów
		foreach ($schema as $name => $field) {
			$this->indexMap[$iter++] = $name;
			$this->schema[$name] = $field;
		}
	}

	public function setLabels($labelSchema) {
		$this->labelSchema = $labelSchema;
	}

	/**
	 * Przetwarzanie wiersza wynikowego po poprawnej walidacji.
	 * Programista może przeciążyć tą metodę w swojej klasie, jeżel
	 * potrzebuje dodatkowego filtrowania wyników.
	 * @param array $row
	 * @return array
	 */
	public function postProcessRow($row) {
		return $row;
	}

	/**
	 * Daje dostęp do wszystkich wierszy oczyszczonych.
	 * @param array $rows
	 */
	public function postProcessAllRows($cleanedRows)
	{
		return $cleanedRows;
	}

	/**
	 * Definicja schematu pliku odbywa się tutaj.
	 * @return
	 */
	abstract public function setup();

	/**
	 * Przetwarzanie pliku XLS.
	 * @param string $excelFilepath Scieżka do pliku
	 * @return array wyniki
	 */
	public function process($excelFilepath, $outputEncoding='cp1250')
	{
            APPPATH.'vendor/PHPExcel-1.7.2/Classes/PHPExcel/IOFactory.php';
             //require_once APPPATH."forms/SRM/Gielda/Form/NowyProdukt.php";
		require_once APPPATH.'vendor/PHPExcel-1.7.2/Classes/PHPExcel/IOFactory.php';
		$xls = PHPExcel_IOFactory::load($excelFilepath);

		$cleanedRows = array();

		$sheet = $xls->getSheet(0);
		$rowIterator = $sheet->getRowIterator();


		foreach ($rowIterator as $row)
		{
			// omiń pierwszy wiersz jeżeli tak ustawiono
			if ($this->omitFirstRow) {
				if ($row->getRowIndex() == 1) {
					continue;
				}
			}
			try
			{
				$rowData = array();
				foreach ($this->indexMap as $index => $name)
				{
					$cell = $sheet->getCellByColumnAndRow($index-1,$row->getRowIndex());
					$calculatedValue = $cell->getCalculatedValue();
					// specjalnie potraktuj komórkę typu date time
					if (PHPExcel_Shared_Date::isDateTime($cell)) {
						$calculatedValue = PHPExcel_Shared_Date::ExcelToPHP($calculatedValue);
					}
					$rowData[$name] = $calculatedValue;
				}

				// wykonaj właściwą walidację danych
				$cleanedRow = $this->schema->clean($rowData);

				if ($this->doGlobalEscapeHTML) {
					foreach ($cleanedRow as $key => $field) {
						$cleanedRow[$key] = htmlspecialchars($field);
					}
				}

				if ($this->doPostprocessing) {
					$cleanedRow = $this->postProcessRow($cleanedRow);
				}

				$cleanedRows[] = $cleanedRow;
			}
			// każdy wiersz, który nie przeszedł walidacji trafia
			// do listy błędów
			catch (sfValidatorError $e) {
				$errorCells = array();
				foreach ($e->getNamedErrors() as $fieldName => $error) {
					$errorCells[$fieldName] = array(
						'message' => $error->getMessage(),
						'cellIndex' => $this->buildCellIndex($row->getRowIndex(), $this->_getFieldIndex($fieldName)),
						'label' => $this->getLabel($fieldName)
					);
				}

				$this->addRowError(
					$row->getRowIndex(),
					$rowData,
					$fieldIndex,
					$e->getCode(),
					$errorCells
				);
			}
		}

		$postProcessedRows = $this->postProcessAllRows($cleanedRows);

		if ($this->raiseExceptionOnError)
		{
			if (!empty($this->errorRows)) {
				throw new SRM_Data_Import_Error($this->errorRows);
			}
		}

		return $postProcessedRows;
	}

	/**
	 * Ustawiane w momencie przetworzenia pliku - jeżeli którykolwiek z wierszy
	 * nie przeszedł walidacji to zostanie zwrócony w tej tablicy
	 * @return array
	 */
	public function getErrorRows() {
		return $this->errorRows;
	}

	/**
	 * Zwraca indeks pola o podanej nazwie
	 * @param stirng $fieldName
	 * @return int
	 */
	protected function _getFieldIndex($fieldName) {
		foreach ($this->indexMap as $index => $name) {
			if ($fieldName == $name) {
				return $index;
			}
		}
	}

	public function getLabel($fieldName) {
		if (array_key_exists($fieldName, $this->labelSchema)) {
			return $this->labelSchema[$fieldName];
		}
		else {
			return $fieldName;
		}
	}

	/**
	 * Tworzy indeks komórki dla podanego wiersza i kolumn np. A1, F3
	 * @param int $row
	 * @param int $column
	 * @return string
	 */
	protected function buildCellIndex($row, $column) {
		return chr(ord('A') + ($column-1)) . $row;
	}

	/**
	 *
	 */
	protected function addRowError($index, $row, $column, $code, $errorCells)
	{
		$this->errorRows[] = array(
			'index' => $index,
			'row' => $row,
			'column' => $column,
			'code' => $code,
			'errorCells' => $errorCells
		);
	}
}



/*
-----------------------
W kontrolerze

$reader = new SRM_Data_Import_Produkty_Produkty(false);
if (request::method() == 'POST') {
	// maks. rozmiar pliku to 5 MB
	$importFileMaxSize = 1024*1024*5;

	try {
		$uploadValidator = new sfValidatorFile(array(
			'max_size' => $importFileMaxSize ,
		'mime_types' => array(
		'application/vnd.ms-excel',
		'application/vnd',
		'application/excel',
		'application/x-excel',
		'application/x-msexcel'
		)
		), array(
			'max_size' => __('lang_plik_jest_za_duzy'),
			'mime_types' => __('lang_tylko_pliki_xls')
		));

		$validatedFile = $uploadValidator->clean($_FILES['zalacznik_produkty']);

		try {
			$produkty = $reader->process($validatedFile->getTempName());
			//$produkty = $reader->process('C:\xampp\tmp\import_produkty.xls'); // tylko do testów na windows

			// $produkty to tablica z pozycjami o strukturze zdefiniowanej w schemacie
		}
		catch (SRM_Data_Import_Error $e) {
			$this['nieprawidlowe_wiersze'] = $e->getErrorRows();
		}
	}
	catch (sfValidatorError $e) {
		$tpl->assign('error', $e->getMessage());
	}
}

------------
Definicja klasy


class SRM_Data_Import_Produkty_Produkty extends SRM_Data_Import_ExcelSchema {
	public function setup()
	{
		sfValidatorString::setDefaultMessage('required', __('lang_pole_jest_wymagane'));

		$this->setLabels(array(
			'nazwa'  => strtolower(__('lang_nazwa')),
			'indeks' => strtolower(__('lang_indeks'))
		));

		$this->setSchema(array(
			'nazwa'  => new sfValidatorString(array('required' => true)),
			'indeks' => new sfValidatorString(array('required' => true))
		));
	}



}
 *
 */
class SRM_Data_Import_Produkty extends SRM_Data_Import_ExcelSchema {
	public function setup()
	{
		//sfValidatorString::setDefaultMessage('required','lang_pole_jest_wymagane');
                //sfValidatorString::setDefaultMessage('required', __('lang_pole_jest_wymagane'));
		/*
		$this->setLabels(array(
			'nazwisko'  => 'lang_nazwa',
			'imie' => 'lang_indeks',
                    'email' => 'lang_indeks'));

		$this->setSchema(array(
			'nazwisko'  => new sfValidatorString(array('required' => true)),
			'imie' => new sfValidatorString(array('required' => true)),
                        'email' => new sfValidatorString(array('required' => true)))
		);
                 *
                 */
	}



}
